package com.csproj.siiat.base.common.util;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import com.csproj.siiat.base.common.util.TLMessage.TLMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.csproj.siiat.config.ApplicationProperties;

import static com.csproj.siiat.base.common.service.TCPReceiverService.MQ;

/**
 * Created by tyshi on 2018/9/26.
 * 
 * this will be main thread to receiving TCP heartbeat from remote
 * then validate and parse the received digits into queque
 * 
 */

public class TCPServer extends Thread {


    private final Logger logger = LoggerFactory.getLogger(TCPServer.class);

    private int port = 0;
   
    private static boolean running = true;
    
    private ServerSocket server = null;

    public TCPServer(int port) {
    	this.port = port;
    	init();
    }

    @Override
    public void run() {

    	logger.info("the TCP server started at port " + this.port);

    	while (running) {

			while (true) {
				TCPSocket client = null;
				try {
					client = new TCPSocket(this.server.accept());
				} catch (IOException e) {
					logger.error("found exception when accepting connections",e);
				}

				new Thread(client).start();
			}
    	}
	}

	private void init() {
		
		if (this.server != null) {
			try {
				server.close();
			} catch (Exception e) {
				logger.error("ignore this close error.", e);
			}
		}
    	try {
			this.server = new ServerSocket(this.port);
		} catch (Exception e) {
			logger.error("Failed to start the TCP receiver socket: ", e);
		}
    	logger.info("TCP server started with  " + this.server.getLocalSocketAddress().toString());
    }

}

class TCPSocket implements Runnable {
	private final Logger logger = LoggerFactory.getLogger(TCPSocket.class);
	private Socket socket;
	private int timer = 31000;
	// if 20 * 30 seconds of time no receiving data from client, close the socket
	private int max_timeout_times = 20;
	private String name = "";

	public TCPSocket(Socket socket) {
		this.socket = socket;
		this.name = socket.getInetAddress() + ":" + socket.getPort();
		logger.info("got connection from " + this.name);
		try {
			this.socket.setSoTimeout(timer);
			this.socket.setKeepAlive(true);
		} catch (SocketException e) {
			logger.error("failed to set the socket timer.", e);
		}

	}

	public void run() {

		boolean done = false;

		int timeout_count = 0;

		while (!done) {

			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream();

				byte[] buf = new byte[1024];
				int n = this.socket.getInputStream().read(buf);
				if( n < 0 ) break;
				baos.write(buf,0,n);
				timeout_count = 0;

				byte[] message = baos.toByteArray();
				if (message.length != TLMessage.C4MessageLength &&
					message.length != TLMessage.C5MessageLength) {
					logger.warn("unsupported message length " + message.length + ", drop the message: " + String.valueOf(message.length));
					continue;
				} else {
					logger.info( this.name + " received message length: " + String.valueOf(message.length) + " body: " + bytesToHex(message));
					TLMessage m = new TLMessage(message);

					if (m == null) {
						logger.error("TLMessage here should not be null, please contact next level support.");
					} else {
						MQ.offer(m);

					}
				}

			}catch (SocketException se) {
				//something wrong with the socket, close the socket
				try {
					this.socket.close();
				} catch (IOException e) {
					logger.error("something wrong during closing the socket.",e);
				}
				logger.error("Connection Reset?, close the sokcet!",se);
				break;
			}
			catch (SocketTimeoutException stoe) {
				timeout_count += 1;
				logger.warn("did not receive data in 31 seconds, wait times count " + timeout_count);
				if (timeout_count >= max_timeout_times) {
					int seconds = timeout_count * 30;
					logger.error("waitted " + seconds + " seconds with no data from client " + this.name);
					logger.error("close this socket");
					try {
						this.socket.close();
					} catch (IOException e) {
						logger.error("something duing closing the socket.",e);
					}
					break;
				}
			} catch ( IOException ste) {
				logger.error("Exception during read input stream. ",ste);
			}
		}
	}
	private byte[] toByteArray(InputStream input) throws IOException {
		byte[] re = null;
		if (input == null) {
			return re;
		}
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		byte[] buffer = new byte[1024];
		int num = input.read(buffer);
		while (num != -1) {
			baos.write(buffer, 0, num);
			num = input.read(buffer);
		}
		baos.flush();
		re = baos.toByteArray();
		baos.close();

		return re;
	}

	public static String bytesToHex(byte[] bytes) {
		String hex= "";
		if (bytes != null) {
			for (Byte b : bytes) {
				hex += String.format("%02X", b.intValue() & 0xFF);
			}
		}
		return hex;
	}



}
